home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-12-11 | 63.6 KB | 2,084 lines | [TEXT/PJMM] |
- { Expressions.p, version 1.0, released December 1993. }
-
- { by David J. Eck }
- { Department of Mathematics }
- { Hobart and William Smith Colleges }
- { Geneva, NY 14456 }
- { E-mail: eck@hws.bitnet }
-
- { This unit can be used in any way, except that: }
- { (1) If you distribute the SOURCE CODE, you cannot charge for it. }
- { (2) If you distribute the source code, modified or unmodified, it must }
- { include the preamble containing my name and address and this restriction, }
- { and it must include a note of any changes made. }
- { Note that there is no restriction on distributing programs you write using }
- { this unit, or even charging for them. }
-
- { This unit has been tested, but not sufficiently to reveal all errors. I would be }
- { happy to receive reports of problems. However, the unit is provided with no }
- { guarantee of correctness or usefulness. }
-
-
-
- unit expression;
-
- { This unit defines a class EXPRESSION that implements mathematical expressions, }
- { along with a number of supporting utility procedures. Flexibility is provided by }
- { some Boolean variables which can be set to determine the exact behavior of the }
- { unit. }
-
- { IMPORTANT NOTE: If you want to use this unit in a program you MUST include a }
- { call to the procedure InitExpressions in the initialization }
- { section of your program. }
-
-
- { SPECS: 1) Expressions can include the operators: +, -, *, /, and ^. }
- { 2) The available built-in functions are: sin, cos, tan, cot, csc, sec, }
- { arcsin,arctan,exp,ln,sqrt,cubert,abs,round,trunc. }
- { 3) By default, brackets and braces can be used, as well as parentheses. }
- { (You can turn off this option.) }
- { 4) Options that you can turn on include: factorials, split functions, }
- { and multiplication by juxtaposition instead of * }
- { 5) You can have user-defined functions with up to 10 arguments. }
- { 6) The word "pi" is reserved to mean the constant π. (The symbol π itself }
- { can also be used in expressions. }
- { 7) For more information, see the boolean OPTIONS defined below. }
-
-
- interface
-
- const
-
- symbolNameMaxLength = 20; { This is the longest a variable or function name can }
- { be; extra characters in a name are discarded on input. This must be at least }
- { 6 in order to support the names of the standard functions. }
-
- infinity = 1e2000; { The value of an expression that is undefined will be either }
- errorVal = 1e2001; { infinity or errorVal; errorVal is used when an input }
- { value is not in the domain of a function. }
- infinityRecip = 1e-2000; { Just for convenience; must be set to 1/infinity }
-
-
-
- {-------------------- Definition of the class Expression ------------------------}
-
- type
-
- expression = object
- data: handle; { An encoding of the actual expression, really of type }
- { ExpressionListHandle, which is hidden in the implementation }
- { section of this unit. YOU SHOULD NOT do anything with the }
- { instance variables of an expression. }
- count: integer; { number of nodes in the ExpressionListHandle }
- first: integer; { the number of the "root" node in the ExpressionListHandle }
- procedure createFromString (definition: string;
- var errorPosition: integer;
- var errorMessage: string);
- { This is the most common way of creating an expression. The function is }
- { simply defined from the specified string (such as '3*x-sin(y^2)'). If no }
- { error occurs, then the expression is defined and errorPosition is set to -1 }
- { If an error occurs, then errorPosition is set to the position in the string }
- { where the error was found, and errorMessage is set to a string that }
- { describes the error. }
- procedure createFromText (definition: CharsHandle;
- var errorPosition: integer;
- var errorMessage: string);
- { A limitation on createFromString is that it cannot deal with a definition }
- { containing more than 255 characters. If that is a problem, you can use }
- { createFromText instead. Here, the definition is contained in a CharsHandle, }
- { which is a handle to an array of characters that can be of any length. }
- { Some utility procedures are provided below for manipulating CharsHandles. }
- procedure create (definition: ptr; { pointer to array[0..(charCount-1)] of char }
- charCount: integer;
- var errorPosition: integer;
- var errorMessage: string);
- { This is the basic expression creation procedure, but you probably won't use it }
- { directly; calls to createFromString and createFromText are translated into }
- { calles to this procedure. }
- procedure GetPrintString (var str: string;
- var lengthExceeded: boolean);
- { Returns a string representation of the expression. (This is not necessarily }
- { identical to the string used to create the expression.) It is possible that }
- { the expression would require more than the 255 character maximum allowed }
- { in a string; in that case, the parameter lengthExceeded is set to true, and the }
- { first 255 characters are returned in str. }
- procedure GetPrintText (var theText: CharsHandle);
- { Returns a text representation of the string; here, there is no length maximum. }
- { Note: theText must already exist as a handle; you can create a handle using the }
- { procedure NewChars defined below. }
- procedure kill;
- { Dispose of all storage associated to the expression. After a call to }
- { "expr.kill" any reference to expr is invalid. You can reuse the variable }
- { expr by calling new(expr) again first. }
- function value: extended;
- { Returns the value of the expression. }
- function valueWithCases (var cases: handle): extended;
- { This function also returns the value of the expressions, but every time it }
- { evaluates a discontinuous function, it records which branch of the function }
- { was used in the handle, cases. You can then compare the "cases" from two }
- { successive evaluations of the expression using the procedure SameCases }
- { defined below. If sameCases is false, it is possibly a discontinuity. This }
- { is meant for use in graphing functions, and is really a fudge. }
- { NOTE: cases must already exist as a handle when this procedure is called; }
- { You can create a handle with: cases := NewHandle(0) }
- function isConstant: boolean;
- { Test if this is a constant expression }
- end;
-
-
- {------------------------------ OPTIONS ----------------------------------}
-
- { The following boolean variables are all set to FALSE by the procedure InitExpressions. }
- { Their values affect only the parsing of expressions. For example, if you have defined }
- { a split function, and then you turn the option splitFunctions off, you will still be able }
- { to use the existing split functions (but you won't be able to define new ones). }
- { If you want to change a value, you should ordinarily do so just after calling }
- { InitExpressions. Of course, you can change the values any time you want in your }
- { program, but you should be careful when you do so for some of them, as noted in the }
- { individual comments. }
-
- var
-
- singleLetterVariables: boolean; { if turned on, this restricts variables in expressions }
- { being parsed to consist of a single character. Even if longer variables exist in the }
- { symbol table, they will be inaccessible. }
-
- implicitMultiplication: boolean; { if turned on, then multiplication in expressions }
- { being parsed can be indicated implicitely (i.e. by juxtaposition) as well as }
- { explicitely (by "*"). For example , "speed time" will be interpreted as }
- { "speed * time". Note that a space is still required between speed and time, }
- { since "speedtime" will be interpreted as a single variable. However, if you }
- { also turn on the option singleLetterVariables, then things like "2xy" will be }
- { correctly interpreted (as "2*x*y"). }
-
- autoDeclareVariables: boolean; { by default, when an unknown symbol is encountered }
- { in an expression being parsed, it is considered to be an error. If you turn on this }
- { option, however, any unknown symbol will be automatically declared to be a }
- { variable with an initial value of 0 }
-
- splitFunctions: boolean; { if this option is turned on, it is possible to define "split }
- { functions" which have different defintions on different parts of their domain. }
- { The notation for a split function is: }
- { CASE <condition> : <expression> ; <condition> : <value> ; . . . END }
- { For example: "case x>0: ln(x); x<=0: 1 end" (The final ; is optional.) }
- { Example function definition: "max(a,b) = CASE a>b: a; a<=b: b; END" }
- { When this option is turned on, the words CASE and END are RESERVED. That is }
- { they cannot be used for any other purpose except to define split functions. }
- { (Varialbles or functions named case or end will be inaccessible.) }
-
- parenthesesOnly: boolean; { by default, brackets and set braces can be used in }
- { expressions being parsed. Matching of left bracket to right bracket and left }
- { brace to right brace is enforced, as well as matching of left parenthesis to }
- { right parenthesis. If you turn on this option, only parentheses will be allowed. }
-
- allowFactorials: boolean; { If you turn on this option, then the factorial operator }
- { can appear in expressions being parsed. The notation is the usual one (for }
- { example: n! ). When factorials are evaluated, the operand must be a non- }
- { negative integer, or an error occurs. }
-
- caseSensitive: boolean; { By default, upper case and lower case letters are considered }
- { to be the same during string comparisons. For example, sin(X), Sin(x) and }
- { SIN(x) all mean the same thing. If you want case to matter, you can turn on this }
- { option. If you do, note that the standard functions are written in lower case. }
-
- extraDataAfterExpression: boolean; { Ordinarily, an error occurs if extra data is }
- { found in the input after the expression is fully parsed. Turn on this option if }
- { you don't want this to be an error. }
-
-
- {---------------------------- Procedures ----------------------------------}
-
- procedure initExpressions;
- { This procedure MUST be called before any of the other procedures in the unit are}
- { used. It initializes the symbol table and defined all the built-in functions, as }
- { well as the constant e. }
-
- procedure DefineFunctionFromString (definition: string;
- var errorPos: integer;
- var errorMessage: string);
- { The definition should be a string of the form "<name> (<arguments>) = <expression>" }
- { (for example, like 'f(x,y)=3*x-sin(y)'). The equals sign is actually optional. }
- { This inserts a function <name> into the symbol table with the specified definition. }
- { The function can then be used in subsequent expressions. You can't redefine a }
- { built-in function, and you can't redefine a variable or symbolic constant as a }
- { function. You CAN redefine an existing function, PROVIDED there is the same number }
- { of arguments in the new definition as in the old; if you do redefine a function, any }
- { expression that refers to that function will also be effectively changed. }
- { If the definition is successful, errorPos is set to -1; if an error occurs, errorPos }
- { is set to indicate the position of the error in the string, and errorMessage is set to }
- { a string describing the error. }
-
- procedure DefineFunctionFromText (definition: CharsHandle;
- var errorPos: integer;
- var errorMessage: string);
- { This allows you to define functions when the definition is longer than 255 characters; }
- { here, the defintion is given as a CharsHandle; otherwise, the description of this }
- { routine is the same as that of DefineFunctionFromString. }
-
- procedure DefineFunction (definition: Ptr;
- charCt: integer;
- var errorPos: integer;
- var errorMessage: string);
- { The basic function definition procedure, which you will probably have no reason to }
- { use. Calls to DefineFunctionFromText and DefineFunctionFromString are translated }
- { into calls to this procedure. }
-
- function CreateVariable (name: string;
- val: extended): integer;
- { Add a variable of the specified name, with the specified initial value, to the }
- { symbol table. Thereafter, the variable can be used in expressions. This functions }
- { returns an integer that can be used subsequently to refer to the variable in the procedures }
- { SetVariableName and SetVariableValue. If some error occurs in defining the variable, }
- { then a value of -1 is returned by the function. It is an error to try to redefine an }
- { existing symbol. It is also conceivable (though very unlikely) that an error will }
- { occur because you have run out of memory. }
-
- procedure SetVariableValue (varRef: integer;
- val: extended);
- { Change the value of an existing variable. varRef must be a value returned by the }
- { procedure CreateVariable when the variable was first created. }
-
- procedure SetVariableName (varRef: integer;
- name: string);
- { Change the name of an existing variable. This procedure does no error checking }
- { (so that you could, for example, make two variables have the same name!). Use this }
- { procedure only in limited circumstances. For example, if your program uses only }
- { one or a few variables, and you know what all their names are. }
-
- procedure CreateSymbolicConstant (name: string;
- value: extended;
- var err: boolean);
- { Creates a "symbolic constant", which is like a variable except that its value can't be }
- { changed. The symbolic constant e is built in. (π is also built in, although by a }
- { slightly different mechanism, which allows it to be referred to as either π or pi. ) }
-
- function sameCases (cases1, cases2: handle): boolean;
- { compares two handles returned by the function expression.valueWithCases; if the }
- { answer is false, it is possible that there is a "discontinuity" between the two }
- { evaluations of the expression. See the comment on function valueWithCases above. }
-
- procedure RealToString (x: extended;
- var s: string);
- { Utility procedure for reasonable string representation of a real number. The string }
- { will not be longer than 12 characters. }
-
- function NewChars: CharsHandle;
- { Utility procedure for initializing a CharsHandle; All of the above procedures that }
- { use CharsHandles require that their parameter already be initialized when the }
- { procedure is called; the initial lenght of the array is 0. }
-
- function CharsSize (Chars: CharsHandle): longint;
- { Utility procedure for checking how many characters there are in the array }
- { pointed to by the handle Chars. }
- { Chars must already be initialized, for example by using function NewChars. }
-
- procedure MakeCharsEmpty (var Chars: CharsHandle);
- { Utility procedur for resetting the length of the array pointed to by Chars to 0. }
- { Chars must already be initialized, for example by using function NewChars. }
-
- procedure AddStringToChars (var Chars: CharsHandle;
- str: string);
- { Utility procedure for adding the characters in the string str onto the end of the }
- { array of characters pointed to by Chars. }
- { Chars must already be initialized, for example by using function NewChars. }
-
-
-
- implementation
-
-
- {----------------------- Type definitions for expressions --------------------}
-
- type
-
- ExpressionNodeKinds = ( { types of nodes in the binary tree reprsenting an expression}
- binOpNode, {represents an operator with two operands}
- unaryOpNode, {unary minus or built-in function}
- functNode, {call to user-defined function}
- splitFunctionNode, {represents a sub-expression together with a boolean condition on}
- { the domain; this is also used to implement "split" functions in which }
- { different definitinons are given on different domains }
- variableNode, {represents a variable }
- parameterNode, { ref to a param in a user-defined function; appear only in }
- { the definitions associated with user functions }
- actualParamNode, { an actual parameter in a function call (a sub expression) }
- symbolicConstantNode, { reference to a defined constant, such as e }
- constantNode, { an actual numeric constant }
- piNode { ref to the constant π }
- );
-
- binOpKinds = (plusOp, minusOp, timesOp, divideOp, powerOp, andOp, orOp, leOp, ltOp, geOp, gtOp, eqOp, neOp);
- unaryOpKinds = (unaryMinusOp, notOp, sinOp, cosOp, tanOp, cotOp, secOp, {}
- cscOp, arcsinOp, arctanOp, expOp, lnOp, roundOp, truncOp, sqrtOp,{}
- cubeRtOp, absOp, factorialOp);
-
- ExpressionNode = record { one of the nodes in the binary tree rep. of an expression }
- bracket: char; { parenthesis, brace, or bracket (or space, for no bracket) }
- case kind : ExpressionNodeKinds of
- binOpNode: ( { operator and operands}
- theBinOp: binOpKinds;
- operand1, operand2: integer; { static pointers to operands }
- );
- unaryOpNode: ( {operator/function and operand/argument}
- theOp: unaryOpKinds;
- operand: integer; { static pointer }
- );
- functNode: ( { pointer into list of functions; static pointer to argument }
- definition: integer; { position of definition in symbolTable }
- firstArgument: integer; { ref to first actual parameter; -1 is no params }
- );
- splitFunctionNode: (
- theExpression, theTest: integer; { pointer to subexpression and domain cond.}
- nextCase: integer; { for a split function, the next case subexpression }
- );
- variableNode, symbolicConstantNode: (
- symbol: integer; { pointer into symbol table }
- );
- parameterNode: (
- number: integer
- );
- actualParamNode: (
- param: integer;
- nextArgument: integer;
- );
- constantNode: (
- value: extended
- );
- piNode: (
- )
- end;
-
- ExpressionListArray = array[0..1000] of ExpressionNode; { data for expression is stored }
- ExpressionListPtr = ^ExpressionListArray; { as a binary tree using static pointers in a}
- ExpressionListHandle = ^ExpressionListPtr;{variable-length array of nodes}
-
-
- {---------------------- SYMBOL TABLE STUFF -------------------- }
-
-
- type
-
- symbolTableError = (noSymbolTableError, lowMemory, cantDeleteFunction, symbolDoesNotExist, symbolAlreadyExists);
-
- symbolTableKinds = (variableSymbol, functionSymbol, constantSymbol, builtInFunctionSymbol, deletedSymbol, parameterSymbol);
-
- symbolName = string[symbolNameMaxLength];
-
- symbolTableNode = record
- name: symbolName;
- case kind : symbolTableKinds of
- variableSymbol, constantSymbol: (
- value: extended
- );
- functionSymbol: (
- parameterCount: integer;
- definition: expression
- );
- parameterSymbol: (
- paramNum: integer
- );
- builtInFunctionSymbol: (
- theOp: UnaryOpKinds
- );
- deletedSymbol: (
- )
- end;
- symbolTableArray = array[0..100] of symbolTableNode;
- symbolTablePtr = ^symbolTableArray;
- symbolTableHandle = ^symbolTablePtr;
-
- var
- ST: symbolTableHandle;
- ST_size: integer;
- ST_count: integer;
- ST_mark: integer;
- nameChars: set of char;
-
- procedure MarkSymb;
- begin
- if ST_mark < 0 then
- ST_mark := ST_count;
- end;
-
- procedure FreeSymb;
- begin
- if ST_mark >= 0 then
- ST_count := ST_mark;
- ST_mark := -1;
- end;
-
- function FindSymb (name: SymbolName): integer;
- var
- i: integer;
- begin
- for i := ST_count - 1 downto 0 do
- if not (ST^^[i].kind = deletedSymbol) & EqualString(ST^^[i].name, name, caseSensitive, caseSensitive) then begin
- FindSymb := i;
- EXIT(FindSymb);
- end;
- FindSymb := -1;
- end;
-
- function CreateSymbol (name: SymbolName;
- kind: SymbolTableKinds;
- var err: SymbolTableError): integer;
- var
- loc, i: integer;
- begin
- loc := FindSymb(name);
- if loc <> -1 then begin
- err := symbolAlreadyExists;
- EXIT(CreateSymbol);
- end;
- if (kind <> parameterSymbol) then
- for i := 0 to ST_size - 1 do
- if ST^^[i].kind = deletedSymbol then begin
- loc := i;
- leave
- end;
- if loc = -1 then begin
- if ST_count = ST_size then begin
- SetHandleSize(Handle(ST), (ST_size + 20) * SizeOf(SymbolTableNode));
- if memError <> noErr then begin
- err := lowMemory;
- EXIT(CreateSymbol);
- end;
- ST_size := ST_size + 20;
- end;
- loc := ST_count;
- ST_count := ST_count + 1;
- end;
- ST^^[loc].name := name;
- ST^^[loc].kind := kind;
- if kind = variableSymbol then
- ST^^[loc].value := 0;
- err := noSymbolTableError;
- CreateSymbol := loc;
- end;
-
-
- procedure AddBuiltInFunctions;
- var
- junk: boolean;
- procedure Add (op: unaryOpKinds;
- name: SymbolName);
- var
- where: integer;
- err: symbolTableError;
- begin
- where := CreateSymbol(name, builtInFunctionSymbol, err);
- if err <> noSymbolTableError then
- EXIT(AddBuiltInFunctions);
- ST^^[where].theOp := op;
- end;
- begin
- Add(sinOp, 'sin');
- Add(cosOP, 'cos');
- Add(tanOP, 'tan');
- Add(cscOP, 'csc');
- Add(secOP, 'sec');
- Add(cotOP, 'cot');
- Add(arcsinOP, 'arcsin');
- Add(arctanOP, 'arctan');
- Add(expOP, 'exp');
- Add(lnOP, 'ln');
- Add(roundOP, 'round');
- Add(truncOP, 'trunc');
- Add(sqrtOP, 'sqrt');
- Add(cubertOP, 'cubert');
- Add(absOP, 'abs');
- CreateSymbolicConstant('e', exp(1), junk);
- end;
-
-
- procedure initExpressions;
- begin
- singleLetterVariables := false;
- implicitMultiplication := false;
- autoDeclareVariables := false;
- splitFunctions := false;
- parenthesesOnly := false;
- allowFactorials := false;
- caseSensitive := false;
- extraDataAfterExpression := false;
- ST := SymbolTableHandle(NewHandle(20 * SizeOf(SymbolTableNode)));
- ST_size := 20;
- ST_count := 0;
- ST_mark := -1;
- nameChars := ['a'..'z', 'A'..'Z', '0'..'9', '_'];
- AddBuiltInFunctions;
- end;
-
- function CreateVariable (name: string;
- val: extended): integer;
- { returns ref to variable for use in SetVariableName and SetVariableValue }
- var
- err: SymbolTableError;
- symb: integer;
- begin
- {$PUSH}
- {$R-}
- if length(name) > symbolNameMaxLength then
- name[0] := chr(symbolNameMaxLength);
- {$POP}
- symb := CreateSymbol(name, variableSymbol, err);
- if err <> noSymbolTableError then
- CreateVariable := -1
- else begin
- CreateVariable := Symb;
- ST^^[symb].value := val;
- end;
- end;
-
- procedure SetVariableValue (varRef: integer;
- val: extended);
- begin
- if (varRef < 0) | (varRef >= ST_count) | (ST^^[varRef].kind <> variableSymbol) then
- EXIT(SetVariableValue);
- ST^^[varRef].value := val;
- end;
-
- procedure SetVariableName (varRef: integer;
- name: string);
- { for limited use--little error checking }
- begin
- if (varRef < 0) | (varRef >= ST_count) | (ST^^[varRef].kind <> variableSymbol) then
- EXIT(SetVariableName);
- {$PUSH}
- {$R-}
- if length(name) > symbolNameMaxLength then
- name[0] := chr(symbolNameMaxLength);
- {$POP}
- ST^^[varRef].name := name;
- end;
-
- procedure CreateSymbolicConstant (name: string;
- value: extended;
- var err: boolean);
- var
- STerr: SymbolTableError;
- symb: integer;
- begin
- {$PUSH}
- {$R-}
- if length(name) > symbolNameMaxLength then
- name[0] := chr(symbolNameMaxLength);
- {$POP}
- symb := CreateSymbol(name, constantSymbol, STerr);
- if STerr <> noSymbolTableError then
- err := true
- else begin
- err := false;
- ST^^[symb].value := value;
- end;
- end;
-
- {-------------------END OF SYMBOL TABLE STUFF ------------------ }
-
- function NewChars: CharsHandle; { SOME CHARSHANDLE UTILITIES }
- begin
- NewChars := CharsHandle(NewHandle(0));
- end;
-
- procedure MakeCharsEmpty (var Chars: CharsHandle);
- begin
- SetHandleSize(Handle(Chars), 0);
- end;
-
- function CharsSize (Chars: CharsHandle): longint;
- begin
- CharsSize := GetHandleSize(handle(Chars));
- end;
-
- procedure AddStringToChars (var Chars: CharsHandle;
- str: string);
- var
- start, i: longint;
- begin
- start := GetHandleSize(handle(Chars));
- SetHandleSize(handle(Chars), start + length(str));
- if memError = noErr then
- for i := 1 to length(str) do
- Chars^^[start + i - 1] := str[i];
- end;
-
-
- {---------------------String-reading procs-----------------------}
-
- const
- endOfDataToken = chr(0);
- errorToken = chr(1);
- numericToken = chr(2);
- badNumericToken = chr(3);
- caseToken = chr(4);
- endToken = chr(5);
- implicitStarToken = chr(6);
-
-
- var
- parseData: CharsPtr;
- parseSize: integer;
- pos: integer;
- tokenAvailable: boolean;
- theToken: SymbolName;
- tokenVal: extended;
-
-
-
- function nextCh: char;
- begin
- if pos >= parseSize then
- nextCh := endOfDataToken
- else
- nextCh := parseData^[pos]
- end;
-
- function getCh: char;
- begin
- if pos = parseSize then
- getCh := endOfDataToken
- else begin
- getCh := parseData^[pos];
- pos := pos + 1
- end;
- end;
-
- procedure GetWord (var name: SymbolName);
- { assumes next char is a letter! }
- var
- ch: char;
- ct: integer;
- savePos: integer;
- begin
- ct := 0;
- name := '';
- savePos := pos;
- while (ct < SymbolNameMaxLength) & (nextCh in nameChars) do begin
- name := Concat(name, getCh);
- ct := ct + 1
- end;
- while (nextCh in nameChars) do
- ch := getCh;
- if EqualString(name, 'pi', false, false) then
- name := 'π'
- else if splitFunctions then begin
- if EqualString(name, 'case', false, false) then
- name := caseToken
- else if EqualString(name, 'end', false, false) then
- name := endToken
- else if EqualString(name, 'and', false, false) then
- name := '&'
- else if EqualString(name, 'or', false, false) then
- name := '|'
- else if EqualString(name, 'not', false, false) then
- name := '~'
- else if singleLetterVariables & (FindSymb(name) = -1) then begin
- name := name[1];
- pos := savePos + 1;
- end;
- end
- else if singleLetterVariables & (FindSymb(name) = -1) then begin
- name := name[1];
- pos := savePos + 1;
- end;
- end;
-
- procedure GetNum (var val: extended;
- var good: boolean);
- var
- num: string;
- ct: integer;
- begin
- num := '';
- good := false;
- while (length(num) < 255) & (nextCh in ['0'..'9']) do
- num := Concat(num, getCh);
- if nextCh = '.' then begin
- if num = '' then begin
- num := getCh;
- if not (nextCh in ['0'..'9']) then
- EXIT(GetNum) { '.' with no digits on either side of it }
- end
- else if length(num) < 255 then
- num := Concat(num, getCh)
- end;
- while (length(num) < 255) & (nextCh in ['0'..'9']) do
- num := Concat(num, getCh);
- if (length(num) < 255) & ((nextCh = 'e') | (nextCh = 'E')) then begin
- num := Concat(num, getCh);
- if (length(num) < 255) & ((nextCh = '-') | (nextCh = '+')) then
- num := Concat(num, getCh);
- ct := 0;
- while (length(num) < 255) & (nextCh in ['0'..'9']) do begin
- num := Concat(num, getCh);
- ct := ct + 1
- end;
- if (ct = 0) | (ct > 3) then
- EXIT(GetNum); { bad number of digits in exponent }
- end;
- if length(num) = 255 then
- EXIT(GetNum); {number too long}
- IOCheck(false);
- ReadString(num, val);
- IOCheck(true);
- if IOResult <> 0 then
- EXIT(GetNum); { something strange is wrong in the number }
- good := true;
- end;
-
- procedure look (var token: SymbolName);
- var
- ch: char;
- good: boolean;
- begin
- if tokenAvailable then
- token := theToken
- else begin
- ch := nextCh;
- while ch in [' ', chr(9), chr(13), chr(3)] do begin
- ch := getCh;
- ch := nextCh;
- end;
- if ch in ['0'..'9', '.'] then begin
- GetNum(tokenVal, good);
- if good then
- theToken := numericToken
- else
- theToken := badNumericToken
- end
- else if ch in ['a'..'z', 'A'..'Z'] then
- GetWord(theToken)
- else if ch in [endOfDataToken, 'π', '+', '-', '*', '^', '/', '(', ')', '[', ']', '{', '}', '='] then
- theToken := GetCh
- else if allowFactorials & (ch = '!') then
- theToken := GetCh
- else if splitFunctions & (ch in ['~', '<', '>', '≥', '≤', '≠', '&', '|', ':', ';', ',']) then begin
- theToken := getCh;
- if (theToken = '>') & (nextCh = '=') then begin
- theToken := '≥';
- ch := getCh
- end
- else if (theToken = '<') & (nextCh = '=') then begin
- theToken := '≤';
- ch := getCh
- end
- else if (theToken = '<') & (nextCh = '>') then begin
- theToken := '≠';
- ch := getCh
- end
- end
- else begin
- theToken := errorToken;
- ch := getCh
- end;
- token := theToken;
- tokenAvailable := true;
- end
- end;
-
- procedure GetToken (var token: symbolName);
- begin
- Look(token);
- TokenAvailable := false;
- end;
-
-
- {----------------end of tokenization procedures--------------------}
- function RightBracket (left: char): char;
- begin
- if left = '(' then
- RightBracket := ')'
- else if left = '{' then
- RightBracket := '}'
- else if left = '[' then
- RightBracket := ']';
- end;
-
- procedure DefineFunctionFromString (definition: string;
- var errorPos: integer;
- var errorMessage: string);
- begin
- if definition = '' then begin
- errorPos := 0;
- errorMessage := 'Empty input provided for function definition.';
- end
- else
- DefineFunction(@definition[1], length(definition), errorPos, errorMessage);
- end;
-
- procedure DefineFunctionFromText (definition: CharsHandle;
- var errorPos: integer;
- var errorMessage: string);
- begin
- Hlock(Handle(definition));
- DefineFunction(Ptr(definition^), CharsSize(definition), errorPos, errorMessage);
- HUnlock(Handle(definition));
- end;
-
- procedure DefineFunction (definition: Ptr;
- charCt: integer;
- var errorPos: integer;
- var errorMessage: string);
- var
- name, tok: SymbolName;
- paramCt: integer;
- err: SymbolTableError;
- exp: expression;
- symb, func: integer;
- nameExists: boolean;
- procedure ExitWithError (message: string);
- begin
- errorPos := pos;
- errorMessage := message;
- FreeSymb;
- EXIT(DefineFunction);
- end;
- begin
- parseData := CharsPtr(definition);
- parseSize := charCt;
- pos := 0;
- TokenAvailable := false;
- GetToken(name);
- if not (name[1] in ['a'..'z', 'A'..'Z']) then
- ExitWithError('Illegal name specified for function begin defined.');
- symb := FindSymb(name);
- if symb = -1 then
- NameExists := false
- else if ST^^[symb].kind <> functionSymbol then
- ExitWithError('The name for the function being defined is already in use.')
- else begin
- NameExists := true;
- func := symb
- end;
- GetToken(tok);
- if tok <> '(' then
- ExitWithError('Expected a left parenthesis to begin the function''s argument list.');
- GetToken(tok);
- if not (tok[1] in ['a'..'z', 'A'..'Z']) then
- ExitWithError('Expected a name for the function''s first argument.');
- paramCt := 0;
- MarkSymb;
- repeat
- paramCt := paramCt + 1;
- if paramCt > 10 then
- ExitWithError('Too many arguments for this function; maximum is ten.');
- symb := CreateSymbol(tok, parameterSymbol, err);
- if err = lowMemory then
- ExitWithError('Ran out of memory.')
- else if err = symbolAlreadyExists then
- ExitWithError('You can''t have two arguments with the same name.');
- ST^^[symb].paramNum := paramCt;
- GetToken(tok);
- if (tok <> ',') & (tok <> ')') then
- ExitWithError('Expected either a comma or a right parenthesis.');
- if tok = ',' then begin
- GetToken(tok);
- if not (tok[1] in ['a'..'z', 'A'..'Z']) then
- ExitWithError('Expected a name for the function''s next argument.');
- end;
- until tok = ')';
- look(tok);
- if tok = '=' then
- GetToken(tok);
- if nameExists & (ST^^[func].parameterCount <> paramCt) then
- ExitWithError('Attempt to redefine a function with a different number of arguments.');
- new(exp);
- definition := Ptr(longint(definition) + pos);
- exp.create(definition, charCt - pos, errorPos, errorMessage);
- FreeSymb;
- if errorPos >= 0 then begin
- errorPos := errorPos + pos;
- dispose(exp);
- Exit(DefineFunction);
- end;
- if not nameExists then begin
- func := CreateSymbol(name, functionSymbol, err);
- if err <> noSymbolTableError then begin
- exp.kill;
- ExitWithError('Ran out of memory.');
- end;
- end;
- ST^^[func].parameterCount := paramCt;
- if nameExists then
- ST^^[func].definition.kill;
- ST^^[func].definition := exp;
- end;
-
- procedure expression.create (definition: ptr; { pointer to array[0...] of char }
- charCount: integer;
- var errorPosition: integer; { -1 if no error }
- var errorMessage: string); { unchanged if no error }
- var
- size: integer;
- exp: expressionListHandle;
- procedure ExitWithError (message: string);
- begin
- DisposHandle(data);
- errorPosition := pos;
- errorMessage := message;
- EXIT(create);
- end;
- function NewNode: integer;
- begin
- if count = size then begin
- SetHandleSize(data, (size + 20) * SizeOf(expressionNode));
- if memError <> noErr then
- ExitWithError('There is not enough memory to create the expression.');
- size := size + 20;
- end;
- exp^^[count].bracket := ' ';
- NewNode := count;
- count := count + 1;
- end;
- function CreateUnaryOpNode (theOp: unaryOpKinds;
- operand: integer): integer;
- var
- loc: integer;
- begin
- loc := NewNode;
- exp^^[loc].kind := unaryOpNode;
- exp^^[loc].theOp := theOp;
- exp^^[loc].operand := operand;
- CreateUnaryOpNode := loc;
- end;
- function CreateBinOpNode (theOp: binOpKinds;
- operand1, operand2: integer): integer;
- var
- loc: integer;
- begin
- loc := NewNode;
- exp^^[loc].kind := binOpNode;
- exp^^[loc].theBinOp := theOp;
- exp^^[loc].operand1 := operand1;
- exp^^[loc].operand2 := operand2;
- CreateBinOpNode := loc;
- end;
- procedure expression (var loc: integer;
- var logical: boolean);
- forward;
- procedure primary (var loc: integer;
- var logical: boolean);
- var
- tok, brak, saveTok: SymbolName;
- symb: integer;
- err: SymbolTableError;
- loc2, loc3, loc4, i: integer;
- procedure CheckBracket (saveTok, tok: SymbolName);
- begin
- if (saveTok = '(') then begin
- if (tok <> ')') then
- ExitWithError('Expected to find a ")" to match a previous "(".');
- end
- else if (saveTok = '{') then begin
- if (tok <> '}') then
- ExitWithError('Expected to find a "}" to match a previous "{".');
- end
- else if (saveTok = '[') then begin
- if (tok <> ']') then
- ExitWithError('Expected to find a "]" to match a previous "[".');
- end;
- end;
- begin
- GetToken(tok);
- if tok = numericToken then begin
- loc := NewNode;
- exp^^[loc].kind := constantNode;
- exp^^[loc].value := tokenVal;
- logical := false;
- end
- else if tok = 'π' then begin
- loc := NewNode;
- exp^^[loc].kind := piNode;
- logical := false;
- end
- else if tok[1] in ['a'..'z', 'A'..'Z'] then begin
- logical := false;
- symb := FindSymb(tok);
- if symb = -1 then
- if autoDeclareVariables then begin
- symb := CreateSymbol(tok, variableSymbol, err);
- if err <> noSymbolTableError then
- ExitWithError('Ran out of memory while trying to declare a new variable.');
- end
- else
- ExitWithError('Unknown name found in expression.');
- case ST^^[symb].kind of
- variableSymbol: begin
- loc := NewNode;
- exp^^[loc].kind := variableNode;
- exp^^[loc].symbol := symb
- end;
- constantSymbol: begin
- loc := NewNode;
- exp^^[loc].kind := symbolicConstantNode;
- exp^^[loc].symbol := symb
- end;
- parameterSymbol: begin
- loc := NewNode;
- exp^^[loc].kind := parameterNode;
- exp^^[loc].number := ST^^[symb].paramNum
- end;
- functionSymbol, builtInFunctionSymbol: begin
- Look(brak);
- if (brak <> '(') & (parenthesesOnly | ((brak <> '{') & (brak <> '['))) then
- if parenthesesOnly then
- ExitWithError('The argument to a function must be enclosed in parenthesis.')
- else
- ExitWithError('The argument to a function must be enclosed in parenthesis, brackets, or braces.');
- if ST^^[symb].kind = builtInFunctionSymbol then begin
- GetToken(saveTok);
- expression(loc, logical);
- if logical then
- ExitWithError('The argument to a function cannot be a boolean value.');
- exp^^[loc].bracket := saveTok;
- GetToken(tok);
- CheckBracket(saveTok, tok);
- loc := CreateUnaryOpNode(ST^^[symb].theOp, loc);
- end
- else begin
- GetToken(brak);
- loc := NewNode;
- exp^^[loc].kind := functNode;
- exp^^[loc].definition := symb;
- exp^^[loc].bracket := brak;
- loc2 := loc;
- for i := 1 to ST^^[symb].parameterCount do begin
- expression(loc3, logical);
- if logical then
- ExitWithError('The argument to a function cannot be a boolean value.');
- loc4 := NewNode;
- exp^^[loc4].kind := actualParamNode;
- exp^^[loc4].param := loc3;
- exp^^[loc4].nextArgument := -1;
- if i = 1 then
- exp^^[loc2].firstArgument := loc4
- else
- exp^^[loc2].nextArgument := loc4;
- loc2 := loc4;
- GetToken(tok);
- if i < ST^^[symb].parameterCount then begin
- if (tok = ')') | (tok = '}') | (tok = ']') then
- ExitWithError('Not enough parameters provided for function.')
- else if tok <> ',' then
- ExitWithError('A comma is required between parameters of function.');
- end
- else begin
- if tok = ',' then
- ExitWithError('Too many parameters provided for function.');
- end;
- end;
- CheckBracket(saveTok, tok);
- end;
- end;
- end;
- end
- else if (tok = '(') | (not parenthesesOnly & ((tok = '{') | (tok = '['))) then begin
- saveTok := tok;
- expression(loc, logical);
- exp^^[loc].bracket := saveTok;
- GetToken(tok);
- if (saveTok = '(') then begin
- if (tok <> ')') then
- ExitWithError('Expected to find a ")" to match a previous "(".');
- end
- else if (saveTok = '{') then begin
- if (tok <> '}') then
- ExitWithError('Expected to find a "}" to match a previous "{".');
- end
- else if (saveTok = '[') then begin
- if (tok <> ']') then
- ExitWithError('Expected to find a "]" to match a previous "[".');
- end
- end
- else if tok = caseToken then begin
- loc2 := -1;
- loc := NewNode;
- i := loc;
- repeat
- exp^^[i].kind := splitFunctionNode;
- expression(loc3, logical);
- if not logical then
- ExitWithError('The conditions in a split function must be boolean expressions.');
- GetToken(tok);
- if tok <> ':' then
- ExitWithError('The condition in a split function must be followed by a ":".');
- expression(loc4, logical);
- if logical then
- ExitWithError('You can''t use a boolean expression to compute the value of a split function.');
- if loc2 <> -1 then
- exp^^[loc2].nextCase := i;
- exp^^[i].kind := splitFunctionNode;
- exp^^[i].theTest := loc3;
- exp^^[i].theExpression := loc4;
- exp^^[i].nextCase := -1;
- loc2 := i;
- GetToken(tok);
- if tok = ';' then begin
- look(tok);
- if Tok = endToken then
- GetToken(tok);
- end
- else if tok <> endToken then
- ExitWithError('You need either a ";" or an "end" here.');
- if tok <> endToken then
- i := NewNode;
- until tok = endToken;
- end
- else if tok = endOfDataToken then
- ExitWithError('Incomplete expression; end of data found in middle of expression.')
- else if tok = badNumericToken then
- ExitWithError('An illegally formed number was found.')
- else if tok = errorToken then
- ExitWithError('Illegal item found in expression.')
- else
- ExitWithError('Misplaced symbol found in expression.');
- end;
- procedure factorial (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok: SymbolName;
- begin
- primary(loc, logical);
- if allowFactorials then begin
- look(tok);
- if (tok = '!') & logical then
- ExitWithError('You can''t use the factorial operation on a boolean expression.');
- while tok = '!' do begin
- GetToken(tok);
- loc := CreateUnaryOpNode(factorialOp, loc);
- look(tok);
- end;
- end;
- end;
- procedure factor (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok: SymbolName;
- begin
- factorial(loc, logical);
- look(tok);
- if logical & (tok = '^') then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- while tok = '^' do begin
- GetToken(tok);
- factorial(next, logical);
- if logical then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- loc := CreateBinOpNode(powerOp, loc, next);
- look(tok);
- end;
- end;
- procedure term (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok: SymbolName;
- begin
- factor(loc, logical);
- look(tok);
- if implicitMultiplication & (tok[1] in ['a'..'z', 'A'..'Z', '0'..'9', '[', '{', '(', numericToken]) then
- tok := implicitStarToken;
- if logical & ((tok = '*') | (tok = '/') | (tok = implicitStarToken)) then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- while (tok = '*') | (tok = '/') | (tok = implicitStarToken) do begin
- if tok <> implicitStarToken then
- GetToken(tok);
- factor(next, logical);
- if logical then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- if tok = '/' then
- loc := CreateBinOpNode(divideOp, loc, next)
- else
- loc := CreateBinOpNode(timesOp, loc, next);
- look(tok);
- if implicitMultiplication & (tok[1] in ['a'..'z', 'A'..'Z', '0'..'9', '[', '{', '(', numericToken]) then
- tok := implicitStarToken;
- end;
- end;
- procedure basicExp (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok, leadingTok: SymbolName;
- begin
- look(leadingTok);
- if (leadingTok = '+') | (leadingTok = '-') then
- GetToken(tok);
- term(loc, logical);
- if (leadingTok = '+') | (leadingTok = '-') then begin
- if logical then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- if leadingTok = '-' then
- loc := CreateunaryOpNode(unaryMinusOp, loc);
- end;
- look(tok);
- if logical & ((tok = '+') | (tok = '-')) then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- while (tok = '+') | (tok = '-') do begin
- GetToken(tok);
- term(next, logical);
- if logical then
- ExitWithError('You can''t use a boolean expression with an arithmetic operator.');
- if tok = '+' then
- loc := CreateBinOpNode(plusOp, loc, next)
- else
- loc := CreateBinOpNode(minusOp, loc, next);
- look(tok);
- end;
- end;
- procedure comparison (var loc: integer;
- var logical: boolean);
- var
- loc2: integer;
- tok: SymbolName;
- theOp: binOpKinds;
- begin
- BasicExp(loc, logical);
- look(tok);
- if tok[1] in ['<', '>', '=', '≤', '≥', '≠'] then begin
- if logical then
- ExitWithError('You can''t apply a comparison operator to a boolean expression.');
- GetToken(tok);
- BasicExp(loc2, logical);
- if logical then
- ExitWithError('You can''t apply a comparison operator to a boolean expression.');
- case tok[1] of
- '<':
- theOp := ltOp;
- '>':
- theOp := gtOp;
- '=':
- theOp := eqOp;
- '≤':
- theOp := leOp;
- '≥':
- theOp := geOp;
- '≠':
- theOp := neOp;
- end;
- loc := CreateBinOpNode(theOp, loc, loc2);
- logical := true;
- end;
- end;
- procedure NotExp (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok: SymbolName;
- ct: integer;
- begin
- ct := 0;
- look(tok);
- while tok = '~' do begin
- GetToken(tok);
- ct := ct + 1;
- look(tok);
- end;
- if ct = 0 then
- comparison(loc, logical)
- else begin
- comparison(loc, logical);
- if not logical then
- ExitWithError('You can''t use the NOT operator on an arithmetic expression.');
- if odd(ct) then
- loc := CreateUnaryOpNode(notOp, loc);
- end;
- end;
- procedure andExp (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok: SymbolName;
- begin
- notExp(loc, logical);
- look(tok);
- if not logical & (tok = '&') then
- ExitWithError('You can''t use a boolean operator with an arithmetic expression.');
- while tok = '&' do begin
- GetToken(tok);
- notExp(next, logical);
- if not logical then
- ExitWithError('You can''t use a boolean operator with an arithmetic expression.');
- loc := CreateBinOpNode(andOp, loc, next);
- look(tok);
- end;
- end;
- procedure expression (var loc: integer;
- var logical: boolean);
- var
- next: integer;
- tok: SymbolName;
- begin
- andExp(loc, logical);
- look(tok);
- if not logical & (tok = '|') then
- ExitWithError('You can''t use a boolean operator with an arithmetic expression.');
- while tok = '|' do begin
- GetToken(tok);
- andExp(next, logical);
- if not logical then
- ExitWithError('You can''t use a boolean operator with an arithmetic expression.');
- loc := CreateBinOpNode(orOp, loc, next);
- look(tok);
- end;
- end;
- var
- logical: boolean;
- tok: SymbolName;
- loc: integer;
- begin
- parseData := CharsPtr(definition);
- parseSize := charCount;
- pos := 0;
- tokenAvailable := false;
- data := NewHandle(10 * SizeOf(expressionNode));
- exp := ExpressionListHandle(data);
- if memError <> noErr then begin
- errorPosition := 0;
- errorMessage := 'There is not enough memory to create expression.';
- EXIT(create);
- end;
- size := 10;
- count := 0;
- expression(loc, logical);
- first := loc;
- if logical then
- ExitWithError('A boolean-valued expression is not legal here.');
- Look(tok);
- if (tok <> EndOfDataToken) & not extraDataAfterExpression then
- ExitWithError('Extra data found after the end of the expression.');
- SetHandleSize(data, count * SizeOf(expressionNode));
- errorPosition := -1;
- end;
-
- procedure expression.createFromString (definition: string;
- var errorPosition: integer; { -1 if no error }
- var errorMessage: string);
- begin
- if definition = '' then begin
- errorPosition := 0;
- errorMessage := 'Empty input provided for expression definition.';
- end
- else
- create(@definition[1], length(definition), errorPosition, errorMessage);
- end;
-
- procedure expression.createFromText (definition: CharsHandle;
- var errorPosition: integer; { -1 if no error }
- var errorMessage: string);
- begin
- HLock(Handle(definition));
- create(Pointer(definition^), CharsSize(definition), errorPosition, errorMessage);
- HUnlock(Handle(definition));
- end;
- {$PUSH}
- {$R-}
-
-
- {$S ExtraExpressionStuff }
-
- procedure RealToString (x: extended;
- var s: string);
- var
- n, i: integer;
- begin
- if x = errorVal then
- s := '(ERROR)'
- else if abs(x) <= infinityRecip then
- s := '0'
- else if abs(x) >= infinity then
- s := '?'
- else if (abs(x) >= 5e8) or (abs(x) < 5e-8) then begin { exponential form }
- n := 15;
- repeat { this is needed since the stupid computer alllows 4 spaces for the exponent even if it is one two or three digits }
- s := StringOf(x : n);
- n := n - 1;
- i := length(s);
- while (i > 0) & (s[i] = ' ') do
- i := i - 1;
- s[0] := chr(i);
- until (length(s) <= 12) | (n = 11)
- end
- else begin
- s := StringOf(x : 1 : 10);
- i := length(s);
- while (i > 0) & (s[i] = '0') do { strip off trailing zeros }
- i := i - 1;
- if (i > 0) & (s[i] = '.') then { strip off terminating decimal point }
- i := i - 1;
- if i > 12 then { maximum length allowed for output is 12}
- s[0] := chr(12)
- else
- s[0] := chr(i);
- end
- end;
-
- {$POP}
-
- procedure expression.GetPrintText (var theText: CharsHandle);
- var
- countCh: integer;
- exp: ExpressionListHandle;
- procedure AddString (str: string);
- var
- i: integer;
- begin
- if countCh + length(str) > GetHandleSize(handle(theText)) then
- SetHandleSize(handle(theText), countCh + length(str) + 25);
- if memError <> noErr then begin
- SetHandleSize(handle(theText), count);
- EXIT(GetPrintText);
- end;
- for i := 1 to length(str) do begin
- theText^^[countCh] := str[i];
- countCh := countCh + 1;
- end;
- end;
- function BinName (op: BinOpKinds): string;
- begin
- case op of
- plusOp:
- BinName := ' + ';
- minusOp:
- BinName := ' - ';
- timesOp:
- BinName := '*';
- divideOp:
- BinName := '/';
- powerOp:
- BinName := '^';
- andOp:
- BinName := ' AND ';
- orOp:
- BinName := ' OR ';
- leOp:
- BinName := ' ≤ ';
- ltOp:
- BinName := ' < ';
- geOp:
- BinName := ' ≥ ';
- gtOp:
- BinName := ' > ';
- eqOp:
- BinName := ' = ';
- neOp:
- BinName := ' ≠ ';
- end;
- end;
- function UnaryName (op: UnaryOpKinds): string;
- begin
- case op of
- unaryMinusOp:
- UnaryName := '-';
- notOp:
- UnaryName := ' NOT ';
- sinOp:
- UnaryName := 'sin';
- cosOp:
- UnaryName := 'cos';
- tanOp:
- UnaryName := 'tan';
- cotOp:
- UnaryName := 'cot';
- secOp:
- UnaryName := 'sec';
- cscOp:
- UnaryName := 'csc';
- arcsinOp:
- UnaryName := 'arcsin';
- arctanOp:
- UnaryName := 'arctan';
- expOp:
- UnaryName := 'exp';
- lnOp:
- UnaryName := 'ln';
- roundOp:
- UnaryName := 'round';
- truncOp:
- UnaryName := 'trunc';
- sqrtOp:
- UnaryName := 'sqrt';
- cubertOp:
- UnaryName := 'cubeRt';
- absOp:
- UnaryName := 'abs';
- end;
- end;
- procedure MakeStr (loc: integer);
- var
- i, symb, prm: integer;
- name: SymbolName;
- str: string;
- begin
- if (exp^^[loc].bracket <> ' ') & (exp^^[loc].kind <> functNode) then
- AddString(exp^^[loc].bracket);
- case exp^^[loc].kind of
- binOpNode: begin
- MakeStr(exp^^[loc].operand1);
- AddString(BinName(exp^^[loc].theBinOp));
- MakeStr(exp^^[loc].operand2);
- end;
- unaryOpNode:
- if exp^^[loc].theOp = factorialOp then begin
- MakeStr(exp^^[loc].operand);
- AddString('!');
- end
- else begin
- AddString(UnaryName(exp^^[loc].theOp));
- MakeStr(exp^^[loc].operand);
- end;
- functNode: begin
- symb := exp^^[loc].definition;
- name := ST^^[symb].name;
- AddString(name);
- AddString(exp^^[loc].bracket);
- prm := exp^^[loc].firstArgument;
- for i := 1 to ST^^[symb].paramNum do begin
- if exp^^[prm].kind <> actualParamNode then
- Halt; { ??? }
- MakeStr(exp^^[prm].Param);
- if i < ST^^[symb].paramNum then begin
- AddString(', ');
- prm := exp^^[prm].nextArgument;
- end;
- end;
- AddString(RightBracket(exp^^[loc].bracket))
- end;
- splitFunctionNode: begin
- AddString(' CASE ');
- i := loc;
- repeat
- MakeStr(exp^^[i].theTest);
- AddString(' : ');
- MakeStr(exp^^[i].theExpression);
- i := exp^^[i].nextCase;
- if i >= 0 then
- AddString('; ');
- until i < 0;
- AddString(' END ');
- end;
- variableNode, symbolicConstantNode: begin
- name := ST^^[exp^^[loc].symbol].name;
- AddString(name);
- end;
- constantNode: begin
- RealToString(exp^^[loc].value, str);
- AddString(str);
- end;
- piNode:
- AddString('π');
- end;
- if (exp^^[loc].bracket <> ' ') & (exp^^[loc].kind <> functNode) then
- AddString(RightBracket(exp^^[loc].bracket));
- end;
- begin
- countCh := 0;
- exp := ExpressionListHandle(data);
- MakeStr(first);
- SetHandleSize(handle(theText), countCh);
- end;
-
- procedure expression.GetPrintString (var str: string;
- var lengthExceeded: boolean);
- var
- theText: CharsHandle;
- i: integer;
- top: longint;
- begin
- theText := CharsHandle(NewHandle(25));
- GetPrintText(theText);
- top := GetHandleSize(Handle(theText));
- if top > 255 then begin
- lengthExceeded := true;
- top := 255;
- end
- else
- lengthExceeded := false;
- str := '';
- for i := 0 to top - 1 do
- str := Concat(str, theText^^[i]);
- DisposHandle(Handle(theText));
- end;
-
- procedure expression.kill;
- begin
- DisposHandle(data);
- data := nil;
- dispose(self);
- end;
-
- function power (x: extended;
- n: integer): extended;
- { compute x^n; n MUST be >= 0 !!!}
- var
- v: extended;
- begin
- v := 1;
- while n > 0 do begin
- if odd(n) then begin
- v := v * x;
- if abs(v) > infinity then begin
- v := infinity;
- leave
- end;
- end;
- n := Bsr(n, 1);
- x := sqr(x);
- end;
- power := v;
- end;
-
-
- type
- intListArray = array[0..100] of integer;
- intListPtr = ^IntListArray;
- intListHandle = ^IntListPtr;
- ParamData = array[1..10] of extended;
-
-
- function computeValue (e: expressionListHandle;
- first: integer;
- var cases: Handle;
- var caseCt, caseSize: integer;
- var context: ParamData): extended;
- var
- caseData: IntListHandle;
- i, j, k: integer;
- function GetVal (loc: integer): extended;
- var
- theCase: longint;
- function BinVal (op: binOpKinds;
- x, y: extended): extended;
- var
- temp: extended;
- Apply2: extended;
- begin
- if op = orOp then begin
- if x <> 0 then
- Apply2 := x
- else
- Apply2 := y
- end
- else if op = andOp then begin
- if (x = 0) then
- Apply2 := 0
- else
- Apply2 := y
- end
- else begin
- if (x = errorVal) or (y = errorVal) then begin
- if op in [eqOp, ltOp, gtOp, NEOp, LEOp, GEOp] then
- Apply2 := 0
- else
- Apply2 := errorVal;
- end
- else if (x = infinity) or (y = infinity) then begin
- if op in [eqOp, ltOp, gtOp, NEOp, LEOp, GEOp] then
- Apply2 := 0
- else
- Apply2 := infinity;
- end
- else if op in [plusOp, minusOp, timesOp, powerOp, divideOp] then begin
- case op of
- plusOp:
- temp := x + y;
- minusOP:
- temp := x - y;
- timesOp:
- temp := x * y;
- divideOp:
- if (abs(y) < infinityRecip) | (abs(x) > abs(infinity * y)) then begin
- temp := infinity;
- theCase := 0;
- end
- else begin
- temp := x / y;
- theCase := ord(y > 0)
- end;
- powerOp:
- if abs(y) <= infinityRecip then begin
- if abs(x) <= infinityRecip then begin
- temp := infinity;
- theCase := 0;
- end
- else begin
- temp := 1;
- theCase := ord(x > 0)
- end
- end
- else if (abs(y) <= 32000) & (abs(round(y) - y) < 1e-5) then begin
- temp := power(x, abs(round(y)));
- if y < 0 then
- if abs(temp) < infinityRecip then
- temp := infinity
- else
- temp := 1 / temp;
- if y < 0 then
- if x = 0 then
- theCase := 0
- else
- theCase := ord(x > 0);
- end
- else begin
- if x = 0 then begin
- temp := 0;
- theCase := 0
- end
- else if x < 0 then begin
- temp := errorVal;
- theCase := -1
- end
- else begin
- temp := y * ln(x);
- if temp < -4000 then
- temp := 0
- else if temp > 4000 then
- temp := infinity
- else
- temp := exp(temp);
- theCase := 1
- end;
- end;
- end;
- if abs(temp) > infinity then
- Apply2 := infinity
- else
- Apply2 := temp
- end
- else
- case op of
- eqOp:
- Apply2 := ord(x = y);
- ltOp:
- Apply2 := ord(x < y);
- gtOp:
- Apply2 := ord(x > y);
- GEOp:
- Apply2 := ord(x >= y);
- LEOp:
- Apply2 := ord(x <= y);
- NEOp:
- Apply2 := ord(x <> y);
- end
- end;
- BinVal := Apply2
- end;
- function UnaryVal (op: unaryOpKinds;
- x: extended): extended;
- { handle the evaluation of a unary operator or built-in function at x}
- var
- temp: extended;
- i: integer;
- apply1: extended;
- begin
- if (abs(x) >= infinity) then
- Apply1 := x
- else begin
- case op of
- unaryMinusOp:
- Apply1 := -x;
- factorialOp: begin
- if (x < -infinityRecip) | (x > 1000) | (abs(x - round(x)) > 1e-10) then begin
- apply1 := errorVal;
- theCase := 1000;
- end
- else begin
- apply1 := 1;
- for i := 2 to round(x) do begin
- apply1 := apply1 * i;
- if apply1 > infinity then begin
- apply1 := infinity;
- leave;
- end;
- end;
- theCase := round(x);
- end
- end;
- sinOp:
- Apply1 := sin(x);
- cosOp:
- Apply1 := cos(x);
- secOp: begin
- temp := cos(x);
- if abs(temp) <= infinityRecip then begin
- Apply1 := infinity;
- theCase := 0
- end
- else begin
- Apply1 := 1 / temp;
- theCase := ord(temp > 0)
- end;
- end;
- cscOp: begin
- temp := sin(x);
- if abs(temp) <= infinityRecip then begin
- Apply1 := infinity;
- theCase := 0;
- end
- else begin
- Apply1 := 1 / temp;
- theCase := ord(temp > 0)
- end;
- end;
- tanOp: begin
- temp := cos(x);
- if abs(temp) <= infinityRecip then begin
- Apply1 := infinity;
- theCase := 0;
- end
- else begin
- Apply1 := sin(x) / temp;
- theCase := ord(temp > 0)
- end;
- end;
- cotOp: begin
- temp := sin(x);
- if abs(temp) <= infinityRecip then begin
- Apply1 := infinity;
- theCase := 0
- end
- else begin
- Apply1 := cos(x) / temp;
- theCase := ord(temp > 0)
- end;
- end;
- arctanOp:
- Apply1 := arctan(x);
- arcsinOp:
- if abs(x) > 1 then begin
- Apply1 := errorVal;
- theCase := 0
- end
- else begin
- theCase := 1;
- if abs(x - 1) < 1e-10 then
- Apply1 := 2 * arctan(1)
- else if abs(x + 1) < 1e-10 then
- Apply1 := -2 * arctan(1)
- else
- Apply1 := arctan(x / sqrt(1 - sqr(x)));
- end;
- lnOp: begin
- if x <= 0 then
- Apply1 := errorVal
- else
- Apply1 := ln(x);
- theCase := ord(x > 0);
- end;
- expOp:
- if x > 4000 then
- Apply1 := infinity
- else if x < -4000 then
- Apply1 := 0
- else
- Apply1 := exp(x);
- absOp: begin
- Apply1 := abs(x);
- if x = 0 then
- theCase := 0
- else
- theCase := ord(x > 0)
- end;
- truncOp:
- if abs(x) >= Maxlongint - 1 then
- Apply1 := errorVal
- else begin
- Apply1 := trunc(x);
- theCase := trunc(x)
- end;
- roundOp:
- if abs(x) >= Maxlongint - 1 then
- Apply1 := errorVal
- else begin
- Apply1 := round(x);
- theCase := round(x)
- end;
- sqrtOp: begin
- if x < 0 then
- Apply1 := errorVal
- else
- Apply1 := sqrt(x);
- theCase := ord(x >= 0);
- end;
- cubertOp:
- if abs(x) < infinityRecip then
- Apply1 := 0
- else if x < 0 then
- Apply1 := -exp(ln(-x) / 3)
- else
- Apply1 := exp(ln(x) / 3);
- end;
- if (abs(x) >= infinity) & (x <> errorVal) then
- UnaryVal := infinity
- else
- UnaryVal := apply1
- end;
- end;
- function FunctVal: extended;
- var
- newContext: ParamData;
- symb, i, ct: integer;
- begin
- symb := e^^[loc].definition;
- i := e^^[loc].firstArgument;
- for ct := 1 to ST^^[symb].parameterCount do begin
- newcontext[ct] := GetVal(e^^[i].param);
- i := e^^[i].nextArgument;
- end;
- with ST^^[symb].definition do
- FunctVal := ComputeValue(expressionListHandle(data), first, cases, caseCt, caseSize, newcontext);
- end;
- var
- x, y: extended;
- uOp: unaryOpKinds;
- bOp: BinOpkinds;
- symb: integer;
- done: boolean;
- ct, i: integer;
- begin
- theCase := maxlongint;
- case e^^[loc].kind of
- binOpNode: begin
- x := GetVal(e^^[loc].operand1);
- y := GetVal(e^^[loc].operand2);
- bOp := e^^[loc].theBinOP;
- GetVal := BinVal(bOp, x, y);
- end;
- unaryOpNode: begin
- x := GetVal(e^^[loc].operand);
- uOp := e^^[loc].theOp;
- GetVal := UnaryVal(uOp, x);
- end;
- constantNode:
- GetVal := e^^[loc].value;
- variableNode, symbolicConstantNode: begin
- symb := e^^[loc].symbol;
- GetVal := ST^^[symb].value
- end;
- splitFunctionNode: begin
- ct := 0;
- done := false;
- repeat
- i := e^^[loc].theTest;
- done := GetVal(i) <> 0;
- if done then
- GetVal := GetVal(e^^[loc].theExpression)
- else
- loc := e^^[loc].nextCase;
- ct := ct + 1;
- until done | (loc = -1);
- if loc = -1 then
- GetVal := errorVal;
- theCase := ct;
- end;
- functNode:
- GetVal := FunctVal;
- parameterNode:
- GetVal := context[e^^[loc].number];
- piNode:
- GetVal := pi;
- end;
- if (cases <> nil) & (theCase <> maxlongint) then begin
- if caseSize = caseCt then begin
- caseSize := caseSize + 20;
- SetHandleSize(cases, caseSize * SizeOf(Integer));
- end;
- if abs(theCase) > maxint then
- theCase := maxint;
- caseData^^[caseCt] := theCase;
- caseCt := caseCt + 1;
- end;
- end;
- begin
- caseData := IntListHandle(cases);
- ComputeValue := getVal(first);
- end;
-
- function expression.value: extended;
- var
- noCases: handle;
- junk: paramData;
- begin
- noCases := nil;
- value := ValueWithCases(noCases)
- end;
-
- function expression.valueWithCases (var cases: handle): extended;
- var
- junk: paramData;
- casesCt, casesSize: integer;
- begin
- if cases <> nil then
- SetHandleSize(cases, 10 * SizeOf(Integer));
- casesCt := 0;
- casesSize := 10;
- valueWithCases := ComputeValue(expressionListHandle(self.data), self.first, cases, casesCt, casesSize, junk);
- if cases <> nil then
- SetHandleSize(cases, casesCt * SizeOf(Integer));
- end;
-
-
- function sameCases (cases1, cases2: handle): boolean;
- var
- ct, i: integer;
- begin
- ct := GetHandleSize(cases1);
- if (ct <> GetHandleSize(cases2)) | (ct mod SizeOf(Integer) <> 0) then
- sameCases := false
- else begin
- sameCases := true;
- for i := 0 to (ct div SizeOf(Integer)) - 1 do
- if intListHandle(cases1)^^[i] <> intListHandle(cases2)^^[i] then begin
- sameCases := false;
- Exit(sameCases);
- end;
- end;
- end;
-
- function expression.isConstant: boolean;
- var
- e: ExpressionListHandle;
- function constant (loc: integer): boolean;
- var
- def: integer;
- begin
- case e^^[loc].kind of
- binOpNode:
- constant := constant(e^^[loc].operand1) & constant(e^^[loc].operand2);
- unaryOpNode:
- constant := constant(e^^[loc].operand);
- functNode: begin
- def := e^^[loc].definition;
- loc := e^^[loc].firstArgument;
- while loc <> -1 do
- if constant(e^^[loc].param) then
- loc := e^^[loc].nextArgument
- else begin
- constant := false;
- EXIT(constant)
- end;
- constant := true;
- end;
- splitFunctionNode: begin
- if not (constant(e^^[loc].theTest) & constant(e^^[loc].theExpression)) then
- constant := false
- else if e^^[loc].nextCase = -1 then
- constant := true
- else
- constant := constant(e^^[loc].nextCase);
- end;
- variableNode:
- constant := false;
- symbolicConstantNode, constantNode, piNode:
- constant := true;
- end;
- end;
- begin
- e := ExpressionListHandle(data);
- isConstant := constant(first);
- end;
-
-
- end.